package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/gdamore/tcell/v2"
	"github.com/mitchellh/go-homedir"
	"github.com/pelletier/go-toml/v2"
)

type Opts struct {
	HighlightColor   string `toml:"highlight_color"`
	TextColor        string `toml:"text_color"`
	BorderColor      string `toml:"border_color"`
	TerminalLauncher string `toml:"terminal_launcher"`
	Cursor           string `toml:"cursor"`
	HardStop         bool   `toml:"hard_stop"`
	LogLevel         string `toml:"log_level"`
	Help             bool
	Version          bool
	ClearHistory     bool
}

type App struct {
	Name        string   `json:"name"`
	Command     string   `json:"command"`
	Description string   `json:"description"`
	IsTerminal  bool     `json:"is_terminal"`
	Path        *string  `json:"path,omitempty"`
	Score       int      `json:"-"`
	History     uint64   `json:"history"`
	LastRun     string   `json:"last_run"`
	Actions     []string `json:"actions"`
	IsPathApp   bool     `json:"is_path_app"`
}

type HistoryEntry struct {
	Name    string    `json:"name"`
	Count   uint64    `json:"count"`
	LastRun time.Time `json:"last_run"`
}

var (
	opts          Opts
	apps          []App
	selectedIdx   int
	query         string
	historyPath   string
	logFile       *os.File
	screen        tcell.Screen
	filterMode    string
	defaultConfig = `
highlight_color = "#5294e2"
text_color = "#ffffff"
border_color = "#3a3a3a"
terminal_launcher = "st -e"
cursor = "█"
hard_stop = false
log_level = ""
`
)

func main() {
	if err := initLogging(); err != nil {
		fmt.Fprintf(os.Stderr, "日志初始化失败: %v\n", err)
		os.Exit(1)
	}
	defer logFile.Close()

	logInfo("程序启动")

	if err := realMain(); err != nil {
		logError("程序错误: %v", err)
		fmt.Fprintf(os.Stderr, "错误: %v\n", err)
		fmt.Println("按回车键退出...")
		var input string
		fmt.Scanln(&input)
		os.Exit(1)
	}
}

func realMain() error {
	filterMode = "all"
	parseCLI()
	if opts.Help {
		printUsage()
		return nil
	}
	if opts.Version {
		fmt.Println("gyr 0.3.0")
		return nil
	}

	configPath, err := getDefaultConfigPath()
	if err != nil {
		return fmt.Errorf("获取配置路径失败: %w", err)
	}
	if err := ensureConfigFile(configPath); err != nil {
		return fmt.Errorf("确保配置文件存在失败: %w", err)
	}

	if err := loadConfig(configPath); err != nil {
		return fmt.Errorf("加载配置失败: %w", err)
	}

	if err := initHistory(); err != nil {
		return fmt.Errorf("初始化历史记录失败: %w", err)
	}

	if err := loadApps(); err != nil {
		return fmt.Errorf("加载应用失败: %w", err)
	}

	if err := initScreen(); err != nil {
		return fmt.Errorf("初始化终端失败: %w", err)
	}
	defer screen.Fini()

	return runUILoop()
}

func parseCLI() {
	flag.BoolVar(&opts.Help, "help", false, "显示帮助")
	flag.BoolVar(&opts.Help, "h", false, "显示帮助(短)")
	flag.BoolVar(&opts.Version, "version", false, "显示版本")
	flag.BoolVar(&opts.Version, "V", false, "显示版本(短)")
	flag.BoolVar(&opts.ClearHistory, "clear-history", false, "清除历史记录")
	flag.Parse()
}

func printUsage() {
	fmt.Println(`gyr 0.3.0
用法: gyr [选项]
  --clear-history    清除历史记录
  -h, --help         显示帮助
  -V, --version      显示版本
配置文件路径: ~/.config/gyr/config.toml
快捷键:
  Ctrl+P/Ctrl+N      上下选择
  Ctrl+F             切换筛选模式(全部/PATH/XDG)
  Esc/Ctrl+C/Ctrl+Q  退出
  Enter/Ctrl+Y       启动应用`)
}

func initLogging() error {
	home, err := homedir.Dir()
	if err != nil {
		return err
	}
	logDir := filepath.Join(home, ".config", "gyr", "logs")
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return err
	}

	logPath := filepath.Join(logDir, fmt.Sprintf("gyr-%s.log", time.Now().Format("20060102")))
	logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	return nil
}

func logInfo(format string, v ...interface{}) {
	if opts.LogLevel == "debug" || opts.LogLevel == "info" {
		msg := fmt.Sprintf("[INFO] %s: %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, v...))
		io.WriteString(logFile, msg)
	}
}

func logError(format string, v ...interface{}) {
	msg := fmt.Sprintf("[ERROR] %s: %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, v...))
	io.WriteString(logFile, msg)
}

func logDebug(format string, v ...interface{}) {
	if opts.LogLevel == "debug" {
		msg := fmt.Sprintf("[DEBUG] %s: %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, v...))
		io.WriteString(logFile, msg)
	}
}

func getDefaultConfigPath() (string, error) {
	home, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(home, ".config", "gyr", "config.toml"), nil
}

func ensureConfigFile(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
			return err
		}
		if err := os.WriteFile(path, []byte(defaultConfig), 0644); err != nil {
			return err
		}
		logInfo("创建默认配置文件: %s", path)
	}
	return nil
}

func loadConfig(path string) error {
	content, err := os.ReadFile(path)
	if err != nil {
		return err
	}

	if err := toml.Unmarshal(content, &opts); err != nil {
		return err
	}

	logInfo("加载配置文件: %s", path)
	return nil
}

func initHistory() error {
	home, err := homedir.Dir()
	if err != nil {
		return err
	}
	historyPath = filepath.Join(home, ".gyr_run_history")

	if opts.ClearHistory {
		if err := os.Remove(historyPath); err != nil && !os.IsNotExist(err) {
			return err
		}
		logInfo("历史记录已清除")
		return nil
	}

	if _, err := os.Stat(historyPath); os.IsNotExist(err) {
		file, err := os.Create(historyPath)
		if err != nil {
			return err
		}
		file.Close()
		logInfo("创建历史记录文件: %s", historyPath)
	}

	return nil
}

func loadHistory() (map[string]HistoryEntry, error) {
	data, err := os.ReadFile(historyPath)
	if err != nil {
		return nil, err
	}

	if len(data) == 0 {
		return make(map[string]HistoryEntry), nil
	}

	var history map[string]HistoryEntry
	if err := json.Unmarshal(data, &history); err != nil {
		return nil, err
	}

	return history, nil
}

func saveHistory(history map[string]HistoryEntry) error {
	data, err := json.MarshalIndent(history, "", "  ")
	if err != nil {
		return err
	}

	return os.WriteFile(historyPath, data, 0644)
}

func loadApps() error {
	xdgApps, err := loadXDGApps()
	if err != nil {
		return err
	}
	logInfo("扫描到 %d 个 XDG .desktop 应用", len(xdgApps))

	pathApps := loadPathApps()
	logInfo("扫描到 %d 个 PATH 可执行应用", len(pathApps))

	appMap := make(map[string]App)
	for _, app := range pathApps {
		appMap[app.Name] = app
	}
	for _, app := range xdgApps {
		appMap[app.Name] = app
	}

	apps = make([]App, 0, len(appMap))
	for _, app := range appMap {
		apps = append(apps, app)
	}

	history, err := loadHistory()
	if err != nil {
		logError("加载历史记录失败: %v", err)
		history = make(map[string]HistoryEntry)
	}
	for i := range apps {
		if entry, ok := history[apps[i].Name]; ok {
			apps[i].History = entry.Count
			apps[i].LastRun = entry.LastRun.Format(time.RFC3339)
		}
	}

	sortAppsByHistory()
	return nil
}

func loadXDGApps() ([]App, error) {
	dirs, err := getXDGAppDirs()
	if err != nil {
		return nil, err
	}

	var wg sync.WaitGroup
	appChan := make(chan App, 100)
	errChan := make(chan error, len(dirs))

	for _, dir := range dirs {
		wg.Add(1)
		go func(d string) {
			defer wg.Done()
			if err := scanDesktopFiles(d, appChan); err != nil {
				errChan <- err
			}
		}(dir)
	}

	go func() {
		wg.Wait()
		close(appChan)
		close(errChan)
	}()

	var xdgApps []App
	for app := range appChan {
		app.IsPathApp = false
		xdgApps = append(xdgApps, app)
	}

	for err := range errChan {
		logError("扫描 XDG 目录错误: %v", err)
	}

	return xdgApps, nil
}

func loadPathApps() []App {
	pathVar := os.Getenv("PATH")
	if pathVar == "" {
		return nil
	}

	seen := make(map[string]struct{})
	var pathApps []App

	for _, dir := range strings.Split(pathVar, ":") {
		entries, err := os.ReadDir(dir)
		if err != nil {
			logDebug("无法读取 PATH 目录 %s: %v", dir, err)
			continue
		}

		for _, entry := range entries {
			if entry.IsDir() {
				continue
			}

			name := entry.Name()
			if _, ok := seen[name]; ok {
				continue
			}
			seen[name] = struct{}{}

			fullPath := filepath.Join(dir, name)
			if isExecutable(fullPath) {
				pathApps = append(pathApps, App{
					Name:        name,
					Command:     fullPath,
					Description: fmt.Sprintf("PATH 命令: %s", fullPath),
					IsTerminal:  true,
					IsPathApp:   true,
				})
			}
		}
	}

	return pathApps
}

func isExecutable(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return info.Mode()&0111 != 0
}

func getXDGAppDirs() ([]string, error) {
	var dirs []string

	if xdgDirs := os.Getenv("XDG_DATA_DIRS"); xdgDirs != "" {
		for _, dir := range strings.Split(xdgDirs, ":") {
			appDir := filepath.Join(dir, "applications")
			if exists, err := dirExists(appDir); err == nil && exists {
				dirs = append(dirs, appDir)
			}
		}
		return dirs, nil
	}

	defaultDirs := []string{
		"/usr/share/applications",
		"/usr/local/share/applications",
	}

	if home, err := homedir.Dir(); err == nil {
		localDir := filepath.Join(home, ".local", "share", "applications")
		defaultDirs = append(defaultDirs, localDir)
	}

	for _, dir := range defaultDirs {
		if exists, err := dirExists(dir); err == nil && exists {
			dirs = append(dirs, dir)
		}
	}

	return dirs, nil
}

func dirExists(path string) (bool, error) {
	info, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	return info.IsDir(), nil
}

func scanDesktopFiles(dir string, appChan chan<- App) error {
	return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		if filepath.Ext(path) != ".desktop" {
			return nil
		}

		content, err := os.ReadFile(path)
		if err != nil {
			logDebug("无法读取 .desktop 文件 %s: %v", path, err)
			return nil
		}

		app, err := parseDesktopFile(string(content), nil)
		if err != nil {
			logDebug("解析 .desktop 文件 %s 失败: %v", path, err)
			return nil
		}
		appChan <- app

		for _, actionName := range app.Actions {
			action := struct{ Name, From string }{actionName, app.Name}
			if actionApp, err := parseDesktopFile(string(content), &action); err == nil {
				appChan <- actionApp
			}
		}

		return nil
	})
}

var execRegex = regexp.MustCompile(` ?%[cDdFfikmNnUuv]`)

func parseDesktopFile(content string, action *struct{ Name, From string }) (App, error) {
	var app App
	section := "[Desktop Entry]"
	if action != nil {
		section = fmt.Sprintf("[Desktop Action %s]", action.Name)
	}

	inSection := false
	lines := strings.Split(content, "\n")

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
			inSection = line == section
			continue
		}

		if !inSection {
			continue
		}

		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue
		}
		key, value := parts[0], parts[1]

		switch key {
		case "Name":
			if action != nil {
				app.Name = fmt.Sprintf("%s (%s)", action.From, value)
			} else {
				app.Name = value
			}
		case "Comment":
			app.Description = value
		case "Terminal":
			app.IsTerminal = value == "true"
		case "Exec":
			app.Command = execRegex.ReplaceAllString(value, "")
		case "NoDisplay":
			if value == "true" {
				return app, errors.New("隐藏应用")
			}
		case "Path":
			app.Path = &value
		case "Actions":
			if action == nil && value != "" {
				app.Actions = strings.Split(value, ";")
			}
		}
	}

	if app.Name == "" {
		app.Name = "未知应用"
	}
	if app.Command == "" {
		return app, errors.New("无命令")
	}

	return app, nil
}

func sortAppsByHistory() {
	sort.Slice(apps, func(i, j int) bool {
		if apps[i].History != apps[j].History {
			return apps[i].History > apps[j].History
		}
		return apps[i].LastRun > apps[j].LastRun
	})
}

func initScreen() error {
	var err error
	screen, err = tcell.NewScreen()
	if err != nil {
		return err
	}
	if err := screen.Init(); err != nil {
		return err
	}

	textColor := getColor(opts.TextColor)
	screen.SetStyle(tcell.StyleDefault.
		Foreground(textColor).
		Background(tcell.ColorBlack))
	screen.Clear()
	return nil
}

func runUILoop() error {
	filteredApps := filterApps(apps, query, filterMode)
	selectedIdx = 0

	for {
		drawUI(filteredApps)

		ev := screen.PollEvent()
		switch ev := ev.(type) {
		case *tcell.EventKey:
			switch ev.Key() {
			case tcell.KeyEsc, tcell.KeyCtrlC, tcell.KeyCtrlQ:
				logInfo("程序退出")
				return nil
			case tcell.KeyEnter, tcell.KeyCtrlY:
				if len(filteredApps) == 0 {
					continue
				}
				return launchApp(&filteredApps[selectedIdx])
			case tcell.KeyBackspace, tcell.KeyBackspace2:
				if len(query) > 0 {
					query = query[:len(query)-1]
					filteredApps = filterApps(apps, query, filterMode)
					selectedIdx = 0
				}
			case tcell.KeyDown, tcell.KeyCtrlN:
				if len(filteredApps) == 0 {
					break
				}
				if selectedIdx < len(filteredApps)-1 {
					selectedIdx++
				} else if !opts.HardStop {
					selectedIdx = 0
				}
			case tcell.KeyUp, tcell.KeyCtrlP:
				if len(filteredApps) == 0 {
					break
				}
				if selectedIdx > 0 {
					selectedIdx--
				} else if !opts.HardStop {
					selectedIdx = len(filteredApps) - 1
				}
			case tcell.KeyLeft:
				selectedIdx = 0
			case tcell.KeyRight:
				if len(filteredApps) > 0 {
					selectedIdx = len(filteredApps) - 1
				}
			case tcell.KeyCtrlF:
				switch filterMode {
				case "all":
					filterMode = "path"
				case "path":
					filterMode = "xdg"
				case "xdg":
					filterMode = "all"
				}
				filteredApps = filterApps(apps, query, filterMode)
				selectedIdx = 0
			default:
				if ev.Rune() != 0 {
					query += string(ev.Rune())
					filteredApps = filterApps(apps, query, filterMode)
					selectedIdx = 0
				}
			}
		case *tcell.EventResize:
			screen.Sync()
		}
	}
}

func filterApps(allApps []App, query string, mode string) []App {
	var baseApps []App
	switch mode {
	case "path":
		for _, app := range allApps {
			if app.IsPathApp {
				baseApps = append(baseApps, app)
			}
		}
	case "xdg":
		for _, app := range allApps {
			if !app.IsPathApp {
				baseApps = append(baseApps, app)
			}
		}
	default:
		baseApps = allApps
	}

	if query == "" {
		sorted := make([]App, len(baseApps))
		copy(sorted, baseApps)
		sort.Slice(sorted, func(i, j int) bool {
			if sorted[i].History != sorted[j].History {
				return sorted[i].History > sorted[j].History
			}
			return sorted[i].LastRun > sorted[j].LastRun
		})
		return sorted
	}

	queryLower := strings.ToLower(query)
	var filtered []App

	for _, app := range baseApps {
		nameLower := strings.ToLower(app.Name)
		descLower := strings.ToLower(app.Description)
		cmdLower := strings.ToLower(app.Command)

		var score int

		if strings.Contains(nameLower, queryLower) {
			if nameLower == queryLower {
				score += 100
			} else {
				matchIdx := strings.Index(nameLower, queryLower)
				score += 80 - matchIdx*2
			}
		}

		if strings.Contains(descLower, queryLower) {
			matchIdx := strings.Index(descLower, queryLower)
			score += 50 - matchIdx*1
			if !app.IsPathApp {
				score += 20
			}
		}

		if strings.Contains(cmdLower, queryLower) {
			matchIdx := strings.Index(cmdLower, queryLower)
			score += 30 - matchIdx*1
		}

		if score > 0 {
			app.Score = score
			filtered = append(filtered, app)
		}
	}

	sort.Slice(filtered, func(i, j int) bool {
		scoreI := filtered[i].Score * (int(filtered[i].History) + 1)
		scoreJ := filtered[j].Score * (int(filtered[j].History) + 1)

		if !filtered[i].IsPathApp {
			scoreI += 10
		}
		if !filtered[j].IsPathApp {
			scoreJ += 10
		}

		if scoreI != scoreJ {
			return scoreI > scoreJ
		}
		return filtered[i].Name < filtered[j].Name
	})

	return filtered
}

func max3(a, b, c int) int {
	maxVal := a
	if b > maxVal {
		maxVal = b
	}
	if c > maxVal {
		maxVal = c
	}
	return maxVal
}

func fuzzyScore(s, pattern string) int {
	patternRunes := []rune(pattern)
	sRunes := []rune(s)
	patternLen := len(patternRunes)
	sLen := len(sRunes)

	if patternLen == 0 {
		return 100
	}
	if patternLen > sLen {
		return 0
	}

	score := 0
	patternIdx := 0
	lastMatchPos := -1

	for i, r := range sRunes {
		if r == patternRunes[patternIdx] {
			if lastMatchPos == i-1 {
				score += 50
			} else {
				score += 20
			}

			if i == 0 {
				score += 30
			}

			if i > 0 && (sRunes[i-1] == ' ' || sRunes[i-1] == '-') {
				score += 25
			}

			patternIdx++
			lastMatchPos = i

			if patternIdx == patternLen {
				score += 100
				break
			}
		}
	}

	if patternIdx < patternLen {
		return 0
	}

	return score
}

func drawUI(filteredApps []App) {
	screen.Clear()
	width, height := screen.Size()

	drawBorder(width, height)
	infoHeight := 8
	drawInfoArea(0, 0, width, infoHeight, filteredApps)
	listHeight := height - infoHeight - 3
	drawListArea(0, infoHeight, width, listHeight, filteredApps)
	drawInputArea(0, height-3, width, 3, filteredApps)

	screen.Show()
}

func drawBorder(width, height int) {
	borderColor := getColor(opts.BorderColor)
	style := tcell.StyleDefault.Foreground(borderColor)
	for x := 0; x < width; x++ {
		screen.SetContent(x, 0, tcell.RuneHLine, nil, style)
		screen.SetContent(x, height-1, tcell.RuneHLine, nil, style)
	}
	for y := 0; y < height; y++ {
		screen.SetContent(0, y, tcell.RuneVLine, nil, style)
		screen.SetContent(width-1, y, tcell.RuneVLine, nil, style)
	}
	screen.SetContent(0, 0, tcell.RuneULCorner, nil, style)
	screen.SetContent(width-1, 0, tcell.RuneURCorner, nil, style)
	screen.SetContent(0, height-1, tcell.RuneLLCorner, nil, style)
	screen.SetContent(width-1, height-1, tcell.RuneLRCorner, nil, style)
}

func drawInfoArea(x, y, width, height int, filteredApps []App) {
	textColor := getColor(opts.TextColor)
	highlightColor := getColor(opts.HighlightColor)
	style := tcell.StyleDefault.Foreground(textColor)
	titleStyle := style.Bold(true)

	modeText := fmt.Sprintf("模式: %s | Ctrl+F 切换", filterMode)
	drawText(width-len(modeText)-2, y+1, modeText, style)
	drawText(x+2, y+1, "Gyr 0.3.0", titleStyle)

	if len(filteredApps) > 0 && selectedIdx < len(filteredApps) {
		app := &filteredApps[selectedIdx]

		nameStyle := style.Foreground(highlightColor).Bold(true)
		drawText(x+2, y+3, app.Name, nameStyle)

		descStyle := style
		drawWrappedText(x+2, y+4, width-4, app.Description, descStyle)

		if opts.LogLevel == "debug" {
			yPos := y + 5 + countLines(app.Description, width-4)
			if app.IsTerminal {
				drawText(x+2, yPos, "终端命令: "+app.Command, descStyle)
			} else {
				drawText(x+2, yPos, "命令: "+app.Command, descStyle)
			}
			yPos++

			if app.IsPathApp {
				drawText(x+2, yPos, "来源: PATH", descStyle)
			} else {
				drawText(x+2, yPos, "来源: XDG", descStyle)
			}
			yPos++

			drawText(x+2, yPos, fmt.Sprintf("启动次数: %d", app.History), descStyle)
			yPos++
			drawText(x+2, yPos, fmt.Sprintf("最后运行: %s", app.LastRun), descStyle)
		}
	}
}

func drawListArea(x, y, width, height int, filteredApps []App) {
	textColor := getColor(opts.TextColor)
	highlightColor := getColor(opts.HighlightColor)
	style := tcell.StyleDefault.Foreground(textColor)
	titleStyle := style.Bold(true)
	drawText(x+2, y+1, "应用列表", titleStyle)

	visibleLines := height - 2
	if visibleLines <= 0 {
		return
	}

	start := 0
	if len(filteredApps) > visibleLines {
		start = selectedIdx - (visibleLines / 2)
		if start < 0 {
			start = 0
		}
		if start+visibleLines > len(filteredApps) {
			start = len(filteredApps) - visibleLines
		}
	}

	for i := 0; i < visibleLines && start+i < len(filteredApps); i++ {
		app := &filteredApps[start+i]
		lineY := y + 2 + i
		if lineY >= y+height-1 {
			break
		}

		var itemStyle tcell.Style
		if start+i == selectedIdx {
			itemStyle = style.Background(highlightColor).Foreground(tcell.ColorBlack).Bold(true)
			drawText(x+1, lineY, ">", itemStyle)
		} else {
			itemStyle = style
		}

		name := app.Name
		if app.IsPathApp {
			name += " [PATH]"
		} else {
			name += " [XDG]"
		}
		drawText(x+3, lineY, name, itemStyle)
	}
}

func drawInputArea(x, y, width, height int, filteredApps []App) {
	textColor := getColor(opts.TextColor)
	highlightColor := getColor(opts.HighlightColor)
	style := tcell.StyleDefault.Foreground(textColor)
	promptStyle := style.Foreground(highlightColor)

	status := fmt.Sprintf("(%d/%d) ", selectedIdx+1, len(filteredApps))
	drawText(x+2, y+1, status, style)
	drawText(x+2+len(status), y+1, "> ", promptStyle)

	inputX := x + 2 + len(status) + 2
	drawText(inputX, y+1, query, style)

	cursorX := inputX + len(query)
	screen.SetContent(cursorX, y+1, []rune(opts.Cursor)[0], nil, style)
}

func drawText(x, y int, text string, style tcell.Style) {
	for i, r := range text {
		screen.SetContent(x+i, y, r, nil, style)
	}
}

func drawWrappedText(x, y, maxWidth int, text string, style tcell.Style) int {
	words := strings.Fields(text)
	if len(words) == 0 {
		return 0
	}

	currentX := x
	currentY := y
	lines := 1

	drawText(currentX, currentY, words[0], style)
	currentX += len(words[0])

	for _, word := range words[1:] {
		if currentX+len(word)+1 > x+maxWidth {
			currentY++
			currentX = x
			lines++
		} else {
			currentX++
		}
		drawText(currentX, currentY, word, style)
		currentX += len(word)
	}

	return lines
}

func countLines(text string, maxWidth int) int {
	if text == "" {
		return 0
	}
	words := strings.Fields(text)
	if len(words) == 0 {
		return 0
	}

	currentX := len(words[0])
	lines := 1

	for _, word := range words[1:] {
		if currentX+len(word)+1 > maxWidth {
			lines++
			currentX = len(word)
		} else {
			currentX += len(word) + 1
		}
	}

	return lines
}

func getColor(colorStr string) tcell.Color {
	if strings.HasPrefix(colorStr, "#") {
		hexStr := colorStr[1:]
		hexVal, err := strconv.ParseInt(hexStr, 16, 32)
		if err == nil && len(hexStr) == 6 {
			return tcell.NewHexColor(int32(hexVal))
		}
	}

	color := tcell.GetColor(colorStr)
	if color == tcell.ColorNone {
		return tcell.ColorWhite
	}
	return color
}
func launchApp(app *App) error {
	logInfo("启动应用: %s, 命令: %s", app.Name, app.Command)

	history, err := loadHistory()
	if err != nil {
		logError("加载历史记录失败: %v", err)
		history = make(map[string]HistoryEntry)
	}

	entry, exists := history[app.Name]
	if !exists {
		entry = HistoryEntry{Name: app.Name}
	}
	entry.Count++
	entry.LastRun = time.Now()
	history[app.Name] = entry

	if err := saveHistory(history); err != nil {
		logError("保存历史记录失败: %v", err)
	}

	var cmdParts []string
	if app.IsTerminal {
		cmdParts = append(cmdParts, strings.Fields(opts.TerminalLauncher)...)
	}
	cmdParts = append(cmdParts, strings.Fields(app.Command)...)

	if len(cmdParts) == 0 {
		err := errors.New("无法构建命令")
		// Send failure notification if notify-send exists
		if isNotifySendAvailable() {
			exec.Command("notify-send", "Gyr 启动失败", fmt.Sprintf("应用: %s\n错误: %v", app.Name, err)).Start()
		}
		return err
	}

	if app.Path != nil {
		if err := os.Chdir(*app.Path); err != nil {
			logError("切换目录失败: %v", err)
			err = fmt.Errorf("无法切换目录: %w", err)
			// Send failure notification if notify-send exists
			if isNotifySendAvailable() {
				exec.Command("notify-send", "Gyr 启动失败", fmt.Sprintf("应用: %s\n错误: %v", app.Name, err)).Start()
			}
			return err
		}
	}

	cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
	cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}

	// Execute command and handle result notification
	if err := cmd.Start(); err != nil {
		logError("启动应用失败: %v", err)
		// Send failure notification if notify-send exists
		if isNotifySendAvailable() {
			exec.Command("notify-send", "Gyr 启动失败", fmt.Sprintf("应用: %s\n错误: %v", app.Name, err)).Start()
		}
		return err
	}

	// Send success notification if notify-send exists
	if isNotifySendAvailable() {
		exec.Command("notify-send", "Gyr 启动成功", fmt.Sprintf("应用: %s\n命令: %s", app.Name, app.Command)).Start()
	}

	return nil
}

// New Helper Function: Check if notify-send is available in PATH
func isNotifySendAvailable() bool {
	_, err := exec.LookPath("notify-send")
	return err == nil
}
